package com.uwlaser.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.uwlaser.domain.*;
import com.uwlaser.service.*;
import com.uwlaser.service.dto.*;
import com.uwlaser.service.mapper.*;
import com.uwlaser.utils.BeanCopyUtil;
import com.uwlaser.utils.StringUtils;
import com.uwlaser.utils.enums.DictValueEnum;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zzj
 * @date 2022年5月14日
 */
@Service
@AllArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class OutboundServiceImpl implements OutboundService {

    // 产品
    private final DataMainMapper dataMainMapper;
    // 产品工艺路线
    private final DataProductPathMapper dataProductPathMapper;
    // 产品过站数据
    private final DataPassStationMapper dataPassStationMapper;
    // 产品型号数据
    private final ProductModelMapper productModelMapper;
    // 产品过程数据
    private final DataCourseParamMapper dataCourseParamMapper;
    // 产品过程数据-业务层
    private final DataCourseParamService dataCourseParamService;
    // 工艺参数
    private final CraftParamMapper craftParamMapper;
    // 工艺参数详情
    private final CraftParamDetailMapper craftParamDetailMapper;
    // 工艺工步
    private final CraftStepMapper craftStepMapper;
    // 工艺工步详情
    private final CraftStepDetailMapper craftStepDetailMapper;
    // 产品过程历史数据-业务层
    private final DataCourseParamHistoryService dataCourseParamHistoryService;
    // BOM清单
    private final ProductBomMapper productBomMapper;
    // BOM清单物料
    private final ProductBomDetailMapper productBomDetailMapper;
    // 物料批次数据
    private final DataMaterialBatchMapper dataMaterialBatchMapper;
    // 物料单件数据
    private final DataMaterialSingleMapper dataMaterialSingleMapper;
    // 工单数据
    private final PlanWorkOrderMapper planWorkOrderMapper;
    // 物料批次数据-业务层
    private final DataMaterialBatchService dataMaterialBatchService;
    // 物料单件数据-业务层
    private final DataMaterialSingleService dataMaterialSingleService;
    // 物料批次历史数据-业务层
    private final DataMaterialBatchHistoryService dataMaterialBatchHistoryService;
    // 物料单件历史数据-业务层
    private final DataMaterialSingleHistoryService dataMaterialSingleHistoryService;
    // 产品物料业务对象
    private final ProductMaterialMapper productMaterialMapper;
    /**
     * 产品出站-单个
     * @param requestParam
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultEntity outbound(RequestParam<OutboundParam> requestParam) {

        DataMain condition = new DataMain();
        condition.setProductSn(requestParam.getParam().getProductSn());
        condition.setLienCode(requestParam.getFactoryLine().getModelCode());// 产线编码
        condition.setSectionCode(requestParam.getFactorySection().getModelCode());// 工段编码
        // 获取产品数据
        DataMain dataMain = dataMainMapper.selectOne(new QueryWrapper<>(condition));
        //存在托盘号则需要判定 绑定托盘前校验此托盘是否存在绑定数据，如果存在不允许重复绑定
        if(requestParam.getParam().getTrayCode()!=null){
            DataMain checkTrayBindParam = new DataMain();
            checkTrayBindParam.setTrayCode(requestParam.getParam().getTrayCode());
            //如果是没有托盘号的工站会怎样？
            DataMain trayCode = dataMainMapper.selectOne(new QueryWrapper<>(checkTrayBindParam));
            if(!(trayCode==null)){
                //判断当前出站的是否和绑定的模组一致，如果不一致则说明这个托盘码绑定了其他PACK
                if(!(trayCode.getProductSn().equals(requestParam.getParam().getProductSn()))){
                    throw new RuntimeException(requestParam.getParam().getTrayCode()+"此托盘已经和产品"+trayCode.getProductSn()+"绑定了，请解绑后重试！");
                }
            }
            //绑定托盘号
            dataMain.setTrayCode(requestParam.getParam().getTrayCode());
        }
        if (dataMain == null) throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！MES系统没有维护该产品编码！");
        requestParam.setDataMain(dataMain);

        // 获取产品过站数据
        DataPassStation dataPassStationCondition = new DataPassStation();
        dataPassStationCondition.setProductSn(requestParam.getParam().getProductSn());
        dataPassStationCondition.setLienCode(dataMain.getLienCode());
        dataPassStationCondition.setSectionCode(dataMain.getSectionCode());
        dataPassStationCondition.setStationCode(requestParam.getFactoryStation().getModelCode());
        dataPassStationCondition.setOperationCode(requestParam.getCraftOperation().getOperationCode());
        dataPassStationCondition.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
        //由于生产过程中出现了同一秒内/间隔1秒内同时进来了两个进站请求，导致生成了两条进站记录，此处使用Limit 1做处理，防止报异常 found 2
        DataPassStation dataPassStation = dataPassStationMapper.selectOne(new QueryWrapper<>(dataPassStationCondition).last("limit 1"));
        if (dataPassStation == null) throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！该产品没有过站数据！");


        // 获取工艺参数
        CraftParam cpCondition = new CraftParam();
        cpCondition.setProductCode(dataMain.getProductCode());
        cpCondition.setIsDisable(DictValueEnum.START.getValue());
        CraftParam craftParam = craftParamMapper.selectOne(new QueryWrapper<>(cpCondition));

        List<CraftParamDetail> craftParamDetailList = null;
        // 获取工艺参数详情
        if (craftParam != null) {
            craftParamDetailList = craftParamDetailMapper.selectList(new LambdaQueryWrapper<CraftParamDetail>()
                    .eq(CraftParamDetail::getOperationCode, requestParam.getCraftOperation().getOperationCode())
                    .eq(CraftParamDetail::getParamsId, craftParam.getId())
            );
        }

        // 绑定参数信息（校验参数、二次校验、设置结果、添加产品过程数据）
        bindParamInfo(requestParam, dataPassStation, craftParamDetailList);

        // 绑定物料信息
        bindMaterialInfo(requestParam, dataPassStation);

        if (DictValueEnum.YES.getValue().equals(requestParam.getParam().getIsOutBound())) {// 出站

            // 出站（校验上传的信息是否齐全）
            outBound(requestParam, dataPassStation, craftParamDetailList);
        }

        return new ResultEntity<>(new Object(),"出站成功");
    }

    @Override
    public ResultEntity outboundBatch(RequestParam<OutboundBatchParam> requestParam) {

        // 获取传参
        List<OutboundParam> productSns = requestParam.getParam().getProductSns();

        //判断重复
        HashSet<String> hashSet = new HashSet<>();

        // 单个出站参数
        RequestParam<OutboundParam> request = null;

        // 返回参数对象
        OutboundBatchResult outResult = new OutboundBatchResult();

        for (OutboundParam product : productSns) {
            try {
                //判断是否重复
                if(!hashSet.add(product.getProductSn())){
                    throw new RuntimeException("当前产品序列号【"+product.getProductSn()+"】重复，添加失败！");
                }
                request = new RequestParam<>();
                request.setParam(product);
                request.setDeviceMain(requestParam.getDeviceMain());
                request.setFactoryStation(requestParam.getFactoryStation());
                request.setFactorySection(requestParam.getFactorySection());
                request.setFactoryLine(requestParam.getFactoryLine());
                request.setCraftOperation(requestParam.getCraftOperation());
                request.setOperator(requestParam.getOperator());

                this.outbound(request);// 单个出站

                outResult.addProductSn(product.getProductSn(),"出站成功！", HttpStatus.OK.value());
            } catch (Exception e) {
                outResult.addProductSn(product.getProductSn(),"出站失败！:"+e.getMessage(), HttpStatus.BAD_REQUEST.value());
            }
        }

        return new ResultEntity(outResult);
    }

    /**
     * 绑定参数信息（校验参数、二次校验、设置结果、添加产品过程数据）
     * @param requestParam 请求/公共数据
     * @param dataPassStation 过站数据
     * @param craftParamDetailList 工艺参数详情数据
     */
    private void bindParamInfo(RequestParam<OutboundParam> requestParam, DataPassStation dataPassStation, List<CraftParamDetail> craftParamDetailList) {
        // 获取传参：过程参数列表
        List<OutboundParam.Param> params = requestParam.getParam().getParams();
        if (CollectionUtils.isEmpty(params)) {
            return ;
//            throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！过程参数【params】不能为空！");
        }

        // 产品主表数据
        DataMain dataMain = requestParam.getDataMain();

        // 判断是否重复
        Set<String> collect = params.stream().map(OutboundParam.Param::getParamCode).collect(Collectors.toSet());

        if (params.size() > collect.size()) throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！存在相同参数！");

        // 参数编码集合
        List<String> paramCodeList = params.stream().map(OutboundParam.Param::getParamCode).collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(craftParamDetailList)) {

            // List转Map
            Map<String, OutboundParam.Param> paramMap = params.stream().collect(Collectors.toMap(OutboundParam.Param::getParamCode, Function.identity(), (key1, key2) -> key2));

            // 判断参数是否必传/判断是否需要二次校验
            for (CraftParamDetail cpd : craftParamDetailList) {
                if (DictValueEnum.NO.getValue().equals(cpd.getIsNecessary())) continue;// 非必传

                // 上传的参数
                OutboundParam.Param param = paramMap.get(cpd.getParamCode());

                if (param == null || StringUtils.isEmpty(param.getParamValue())) throw new RuntimeException("params子项：【"+cpd.getParamName()+"】不能为空！");

                // 不需要二次校验
                if (DictValueEnum.NO.getValue().equals(cpd.getIsCheck())) {

                    if (DictValueEnum.RESULT_OK.getValue().equals(param.getParamResult())) {
                        continue;// 判断结果是否为OK
                    } else {
                        // 结果非OK，设置总结果NG，跳出循环。
                        requestParam.getParam().setResult(DictValueEnum.RESULT_NG.getValue());
                        break;
                    }
                }

                BigDecimal bd = new BigDecimal(paramMap.get(cpd.getParamCode()).getParamValue());
                // 对比结果，传值.compareTo(设置值): 结果: -1：小于； 0 ：等于； 1 ：大于；
                boolean re = bd.compareTo(cpd.getParamUpper()) == 1 || bd.compareTo(cpd.getParamLower()) == -1;
                // 设置产品结果（当前只设置NG，出站才会计算总结果）
                if (re) requestParam.getParam().setResult(DictValueEnum.RESULT_NG.getValue());
            }
        }

        // 通过产品SN、工段、产线、工位、设备、工序编码和参数编码查询【产品过程数据】
        List<DataCourseParam> dataCourseParamList = dataCourseParamMapper.selectList(new LambdaQueryWrapper<DataCourseParam>()
//                .in(DataCourseParam::getParamCode, paramCodeList)// 参数编码集合
                .isNotNull(DataCourseParam::getProductSn)
                .eq(DataCourseParam::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                .eq(DataCourseParam::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                .eq(DataCourseParam::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                .eq(DataCourseParam::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                .eq(DataCourseParam::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                .eq(DataCourseParam::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
        );

        // 如果有【产品过程数据】，则添加到历史表中，保留最新的一条【产品过程数据】。
        if (!CollectionUtils.isEmpty(dataCourseParamList)) {

            // 数据转换
            List<DataCourseParamHistory> dataCourseParamHistoryList = JSON.parseArray(JSON.toJSONString(dataCourseParamList), DataCourseParamHistory.class);

            // 保存到【产品过程历史数据】数据
            dataCourseParamHistoryService.saveBatch(dataCourseParamHistoryList);

            // 获取ID集合
            List<String> idList = dataCourseParamList.stream().map(DataCourseParam::getId).collect(Collectors.toList());

            // 删除【产品过程数据】旧数据
            dataCourseParamMapper.deleteBatchIds(idList);
        }

        // 新的【产品过程数据】
        List<DataCourseParam> newDataCourseParamList = JSON.parseArray(JSON.toJSONString(params), DataCourseParam.class);
        // 组装产品过程数据
        for (DataCourseParam e : newDataCourseParamList) {
            e.setProductSn(requestParam.getParam().getProductSn());
            e.setProductCode(dataMain.getProductCode());
            e.setProductType(dataMain.getProductType());
            e.setOrderCode(dataMain.getOrderCode());
            e.setWorkOrderCode(dataMain.getWorkOrderCode());
            e.setLienCode(dataMain.getLienCode());
            e.setSectionCode(dataMain.getSectionCode());
            e.setStationCode(requestParam.getFactoryStation().getModelCode());
            e.setOperationCode(requestParam.getCraftOperation().getOperationCode());
            e.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
            e.setPassStationId(dataPassStation.getId());
            e.setShiftCode(requestParam.getFactoryShift().getShiftCode());
            e.setTeamGroupId(requestParam.getFactoryShift().getTeamGroupId());
            //如果存在容量和档位数据那么就进行填充
            if(dataMain.getGear()!=null&&dataMain.getCapacity()!=null){
                e.setGear(dataMain.getGear());
                e.setCapacity(dataMain.getCapacity());
            }
        }

        // 添加最新的【产品过程数据】
        dataCourseParamService.saveBatch(newDataCourseParamList);
    }

    /**
     * 绑定物料信息
     * @param requestParam 请求/公共数据
     * @param dataPassStation 过站数据
     */
    private void bindMaterialInfo(RequestParam<OutboundParam> requestParam, DataPassStation dataPassStation) {

        // 产品主表数据
        DataMain dataMain = requestParam.getDataMain();

        // 获取上传的物料信息
        List<OutboundParam.Material> materials = requestParam.getParam().getMaterials();

        // 判断是否重复
        Set<String> collect = materials.stream().map(OutboundParam.Material::getMaterialSn).collect(Collectors.toSet());

        if (materials.size() > collect.size()) throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！参数存在相同物料！");

        // 获取BOM制造信息
        ProductBom productBomCondition = new ProductBom();
        productBomCondition.setProductCode(dataMain.getProductCode());
        productBomCondition.setIsDisable(DictValueEnum.START.getValue());
        ProductBom productBom = productBomMapper.selectOne(new QueryWrapper<>(productBomCondition));

        if (!CollectionUtils.isEmpty(materials) && productBom == null) throw new RuntimeException("产品SN【"+dataMain.getProductSn()+"】错误！MES系统没有维护产品型号【"+dataMain.getProductCode()+"】的BOM信息！");
        // 校验BOM明细
        if (productBom != null && !CollectionUtils.isEmpty(materials)) {
            // 获取BOM物料
            List<ProductBomDetail> productBomDetails = productBomDetailMapper.selectList(new LambdaQueryWrapper<ProductBomDetail>()
                    .eq(ProductBomDetail::getBomId, productBom.getId())// BOMid
                    .eq(ProductBomDetail::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
            );
            if (CollectionUtils.isEmpty(productBomDetails)) throw new RuntimeException("产品SN【"+dataMain.getProductSn()+"】错误！MES系统没有维护产品型号【"+dataMain.getProductCode()+"】的BOM明细信息！");

            // 获取参数物料编码集合
            List<String> materialsCodeList = materials.stream().map(OutboundParam.Material::getMaterialCode).collect(Collectors.toList());

            // 获取BOM物料编码集合
            List<String> bomDetailCodeList = productBomDetails.stream().map(ProductBomDetail::getMaterialCode).collect(Collectors.toList());

            // 迭代上传的materialCode集合，校验BOM中是否都配置了此物料的上传
            for (String materialCode : materialsCodeList) {
                if (!bomDetailCodeList.contains(materialCode)) throw new RuntimeException("产品SN【"+dataMain.getProductSn()+"】错误！参数物料编码【"+materialCode+"】不存在！");
            }
            // 获取工步
            CraftStep craftStepCondition = new CraftStep();
            craftStepCondition.setProductCode(dataMain.getProductCode());
            craftStepCondition.setIsDisable(DictValueEnum.START.getValue());
            CraftStep craftStep = craftStepMapper.selectOne(new QueryWrapper<>(craftStepCondition));

            List<CraftStepDetail> craftStepDetailList = null;
            // 获取工步详情
            if (craftStep != null) {
                craftStepDetailList = craftStepDetailMapper.selectList(new LambdaQueryWrapper<CraftStepDetail>()
                        .eq(CraftStepDetail::getStepsId, craftStep.getId())// 工步id
                        .eq(CraftStepDetail::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                );
                // 判断是否有工步（有物料，就需要设置工步信息）
                if (!CollectionUtils.isEmpty(materials) && CollectionUtils.isEmpty(craftStepDetailList)) {
                    throw new RuntimeException("产品SN【"+dataMain.getProductSn()+"】错误！没有设置【工步信息】");
                }
            }

            // 获取批次物料信息
            List<DataMaterialBatch> dataMaterialBatchList = dataMaterialBatchMapper.selectList(new LambdaQueryWrapper<DataMaterialBatch>()
//                    .in(DataMaterialBatch::getMaterialCode, codeList)// 物料编码集合
                    .eq(DataMaterialBatch::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                    .eq(DataMaterialBatch::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                    .eq(DataMaterialBatch::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                    .eq(DataMaterialBatch::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                    .eq(DataMaterialBatch::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                    .eq(DataMaterialBatch::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
            );
            // 获取单件码物料信息
            List<DataMaterialSingle> dataMaterialSingleList = dataMaterialSingleMapper.selectList(new LambdaQueryWrapper<DataMaterialSingle>()
//                    .in(DataMaterialSingle::getMaterialCode, codeList)// 物料编码集合
                    .eq(DataMaterialSingle::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                    .eq(DataMaterialSingle::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                    .eq(DataMaterialSingle::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                    .eq(DataMaterialSingle::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                    .eq(DataMaterialSingle::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                    .eq(DataMaterialSingle::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
            );

            List<DataMaterialBatch> newDataMaterialBatchList = new ArrayList<>();// 批次
            List<DataMaterialSingle> newDataMaterialSingleList = new ArrayList<>();// 单件

            // List转Map
            Map<String, ProductBomDetail> bomDetailMap = productBomDetails.stream().collect(Collectors.toMap(ProductBomDetail::getMaterialCode, Function.identity(), (key1, key2) -> key2));

            // 物料类型：单件码/批次码
            String type;
            // 物料编码
            String code;

            // 组装物料批次数据
            for (OutboundParam.Material m : materials) {

                type = null;
                code = null;

                // 获取工步类型（单件码/批次码）
                if (!CollectionUtils.isEmpty(craftStepDetailList)) {
                    for (CraftStepDetail craftStepDetail : craftStepDetailList) {
                        if (craftStepDetail.getStepNo().toString().equals(m.getScanNumber())) {

                            code = craftStepDetail.getMaterialCode();// 物料编码
                            type = craftStepDetail.getStepType();// 工步类型
                            // 工步类型 转 物料类型
                            if (DictValueEnum.STEP_TYPE_SINGLE.getValue().equals(type)) {
                                type = DictValueEnum.MATERIAL_TYPE_SINGLE.getValue();// 单件码
                            } else if (DictValueEnum.STEP_TYPE_BATCH.getValue().equals(type)) {
                                type = DictValueEnum.MATERIAL_TYPE_BATCH.getValue();// 批次码
                            }
                            break;
                        }
                    }
                }

                // 判断批次和单件物料
                if (DictValueEnum.MATERIAL_TYPE_BATCH.getValue().equals(type)) {// 批次

                    DataMaterialBatch newE = new DataMaterialBatch();
                    // 设置批次ID
                    if (!CollectionUtils.isEmpty(dataMaterialBatchList)) {
                        for (DataMaterialBatch oldE : dataMaterialBatchList) {
                            if (m.getMaterialCode().equals(oldE.getMaterialCode())) {
                                newE.setId(oldE.getId());// 设置ID
                                break;
                            }
                        }
                    }
                    // 设置参数
                    newE.setProductSn(requestParam.getParam().getProductSn());
                    newE.setProductCode(dataMain.getProductCode());
                    newE.setProductType(dataMain.getProductType());
                    newE.setOrderCode(dataMain.getOrderCode());
                    newE.setWorkOrderCode(dataMain.getWorkOrderCode());
                    newE.setLienCode(dataMain.getLienCode());
                    newE.setSectionCode(dataMain.getSectionCode());
                    newE.setStationCode(requestParam.getFactoryStation().getModelCode());
                    newE.setOperationCode(requestParam.getCraftOperation().getOperationCode());
                    newE.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
                    newE.setPassStationId(dataPassStation.getId());
                    newE.setMaterialSn(m.getMaterialSn());
                    newE.setMaterialName(m.getMaterialName());
                    newE.setMaterialCode(m.getMaterialCode());
                    newE.setMaterialUnit(bomDetailMap.get(code).getMaterialUnit());
                    newE.setQuantity(bomDetailMap.get(code).getQuantity());
                    newE.setShiftCode(requestParam.getFactoryShift().getShiftCode());
                    newE.setTeamGroupId(requestParam.getFactoryShift().getTeamGroupId());

                    newDataMaterialBatchList.add(newE);

                } else if (DictValueEnum.MATERIAL_TYPE_SINGLE.getValue().equals(type)) {// 单件

                    DataMaterialSingle newE = new DataMaterialSingle();
                    // 设置批次ID
                    if (!CollectionUtils.isEmpty(dataMaterialSingleList)) {
                        for (DataMaterialSingle oldE : dataMaterialSingleList) {
                            if (m.getMaterialCode().equals(oldE.getMaterialCode())) {
                                newE.setId(oldE.getId());// 设置ID
                                break;
                            }
                        }
                    }
                    // 设置参数
                    newE.setProductSn(requestParam.getParam().getProductSn());
                    newE.setProductCode(dataMain.getProductCode());
                    newE.setProductType(dataMain.getProductType());
                    newE.setOrderCode(dataMain.getOrderCode());
                    newE.setWorkOrderCode(dataMain.getWorkOrderCode());
                    newE.setLienCode(dataMain.getLienCode());
                    newE.setSectionCode(dataMain.getSectionCode());
                    newE.setStationCode(requestParam.getFactoryStation().getModelCode());
                    newE.setOperationCode(requestParam.getCraftOperation().getOperationCode());
                    newE.setDeviceCode(requestParam.getDeviceMain().getDeviceCode());
                    newE.setPassStationId(dataPassStation.getId());
                    newE.setMaterialSn(m.getMaterialSn());
                    newE.setMaterialName(m.getMaterialName());
                    newE.setMaterialCode(m.getMaterialCode());
                    newE.setScanNumber(StringUtils.isEmpty(m.getScanNumber()) ? null : Integer.parseInt(m.getScanNumber()));
                    newE.setMaterialUnit(bomDetailMap.get(code).getMaterialUnit());
//                    newE.setQuantity(bomDetailMap.get(code).getQuantity());
                    newE.setQuantity(1);// 单件码默认是1
                    newE.setShiftCode(requestParam.getFactoryShift().getShiftCode());
                    newE.setTeamGroupId(requestParam.getFactoryShift().getTeamGroupId());

                    newDataMaterialSingleList.add(newE);

                } else { //上传了没有配置工步的物料
                    throw new RuntimeException("产品SN【"+dataMain.getProductSn()+"】错误！" +
                            "物料【"+m.getMaterialName()+"】未配置扫描顺序为"+m.getScanNumber()+"的工步顺序，请查验工步配置和出站scanNumber字段值是否匹配！");
                }
            }

            // 批次物料
            if (!CollectionUtils.isEmpty(dataMaterialBatchList)) {
                // 数据转换
                List<DataMaterialBatchHistory> dataMaterialBatchHistoryList = JSON.parseArray(JSON.toJSONString(dataMaterialBatchList), DataMaterialBatchHistory.class);
                dataMaterialBatchHistoryList.forEach(e -> e.setId(null));// 清空ID，让历史表自动生成
                // 保存至历史表中
                dataMaterialBatchHistoryService.saveBatch(dataMaterialBatchHistoryList);
                // 删除旧批次物料。
                Set<String> idSet = dataMaterialBatchList.stream().map(DataMaterialBatch::getId).collect(Collectors.toSet());
                dataMaterialBatchService.removeByIds(idSet);
            }
            // 添加或更新：批次物料
            if (!CollectionUtils.isEmpty(newDataMaterialBatchList)) {
                dataMaterialBatchService.saveOrUpdateBatch(newDataMaterialBatchList);
            }

            // 单件物料
            if (!CollectionUtils.isEmpty(dataMaterialSingleList)) {
                // 数据转换
                List<DataMaterialSingleHistory> dataMaterialSingleHistoryList = JSON.parseArray(JSON.toJSONString(dataMaterialSingleList), DataMaterialSingleHistory.class);
                dataMaterialSingleHistoryList.forEach(e -> e.setId(null));// 清空ID，让历史表自动生成
                // 保存至历史表中
                dataMaterialSingleHistoryService.saveBatch(dataMaterialSingleHistoryList);
                // 删除旧单件物料。
                Set<String> idSet = dataMaterialSingleList.stream().map(DataMaterialSingle::getId).collect(Collectors.toSet());
                dataMaterialSingleService.removeByIds(idSet);
            }
            // 添加或更新：单件物料
            if (!CollectionUtils.isEmpty(newDataMaterialSingleList)) {
                dataMaterialSingleService.saveOrUpdateBatch(newDataMaterialSingleList);
            }

        }
    }

    /**
     * 出站
     * @param requestParam 请求/公共数据
     * @param dataPassStation 过站数据
     * @param craftParamDetailList 工艺参数详情数据
     */
    private void outBound(RequestParam<OutboundParam> requestParam, DataPassStation dataPassStation, List<CraftParamDetail> craftParamDetailList) {

        // 产品主表数据
        DataMain dataMain = requestParam.getDataMain();

        // 获取【产品过程数据】
        List<DataCourseParam> dataCourseParams = dataCourseParamMapper.selectList(new LambdaQueryWrapper<DataCourseParam>()
                .eq(DataCourseParam::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                .eq(DataCourseParam::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                .eq(DataCourseParam::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                .eq(DataCourseParam::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                .eq(DataCourseParam::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                .eq(DataCourseParam::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
        );

        // 校验【产品过程数据】是否上传（该工位没有配置工艺参数信息则不需要校验）
        if (!CollectionUtils.isEmpty(craftParamDetailList)) {
            if (CollectionUtils.isEmpty(dataCourseParams)) {
                List<String> paramNameList = craftParamDetailList.stream().map(CraftParamDetail::getParamName).collect(Collectors.toList());
                throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】错误！没有上传工艺参数数据【"+String.join(",", paramNameList)+"】！");
            }
            // 配置的工艺参数编码
            List<String> configParamCodeList = craftParamDetailList.stream().filter(e -> DictValueEnum.YES.getValue().equals(e.getIsNecessary())).map(CraftParamDetail::getParamCode).collect(Collectors.toList());
            // 上传的参数编码
            List<String> uploadParamCodeList = dataCourseParams.stream().map(DataCourseParam::getParamCode).collect(Collectors.toList());

            if (configParamCodeList.size() > uploadParamCodeList.size()) {
                configParamCodeList.removeAll(uploadParamCodeList);
                if (!CollectionUtils.isEmpty(configParamCodeList)) {
                    throw new RuntimeException("传入参数与系统过程参数不一致！【"+String.join(",", configParamCodeList)+"】");
                }
            }
        }

        // 获取BOM制造信息
        ProductBom productBomCondition = new ProductBom();
        productBomCondition.setProductCode(dataMain.getProductCode());
        productBomCondition.setIsDisable(DictValueEnum.START.getValue());
        ProductBom productBom = productBomMapper.selectOne(new QueryWrapper<>(productBomCondition));

        // 校验物料
        if (productBom != null) {
            // 获取BOM物料
            List<ProductBomDetail> productBomDetails = productBomDetailMapper.selectList(new LambdaQueryWrapper<ProductBomDetail>()
                    .eq(ProductBomDetail::getBomId, productBom.getId())// BOMid
                    .eq(ProductBomDetail::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                    .eq(ProductBomDetail::getIsNecessary, DictValueEnum.YES.getValue())// 是否必传
            );
            //过滤 批次物料BOM
            List<ProductBomDetail> dataMaterialBatchBom = productBomDetails.stream().filter(p -> p.getCodingType().equals(DictValueEnum.MATERIAL_TYPE_BATCH.getValue())).collect(Collectors.toList());
            // 校验批次物料
            if (!CollectionUtils.isEmpty(dataMaterialBatchBom)) {
                // 获取绑定的批次物料信息
                List<DataMaterialBatch> dataMaterialBatchList = dataMaterialBatchMapper.selectList(new LambdaQueryWrapper<DataMaterialBatch>()
                        .eq(DataMaterialBatch::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                        .eq(DataMaterialBatch::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                        .eq(DataMaterialBatch::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                        .eq(DataMaterialBatch::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                        .eq(DataMaterialBatch::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                        .eq(DataMaterialBatch::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
                );

                //将库中的批次物料绑定记录根据getMaterialCode分组计数
                Map<String, List<DataMaterialBatch>> batchMaterialCodeGroup = dataMaterialBatchList.stream().collect(Collectors.groupingBy(DataMaterialBatch::getMaterialCode));
                //循环判断MES中配置的物料用量和实际上传数量是否一致
                String materialCode=null;
                try{
                    for (ProductBomDetail productBomDetail : dataMaterialBatchBom) {
                        materialCode=productBomDetail.getMaterialCode();
                        //以materialCode为key获取上传物料集合，如果抛NPE就说明BOM中配置的materialCode出站物料集合中未上传
                        List<DataMaterialBatch> dataMaterialBatches = batchMaterialCodeGroup.get(productBomDetail.getMaterialCode());
                        //批次物料无论BOM配置用量是多少出站都只需要传一条物料参数
                        if(dataMaterialBatches.size()!=1){
                            //根据materialCode获取物料对象
                            ProductMaterial productMaterial=new ProductMaterial();
                            productMaterial.setMaterialCode(productBomDetail.getMaterialCode());
                            ProductMaterial localMaterialConfigData = productMaterialMapper.selectOne(new QueryWrapper<>(productMaterial));
                            throw new RuntimeException("上传物料数量与BOM不符！【"+localMaterialConfigData.getMaterialName()
                                    +"】BOM配置为批次码物料，只需上传一个批次物料条码"
                                    +"，实际上传数量为"+batchMaterialCodeGroup.get(productBomDetail.getMaterialCode()).size());
                        }
                    }
                }catch (Exception e){
                    if(e instanceof NullPointerException){
                        //根据materialCode获取物料对象
                        ProductMaterial productMaterial=new ProductMaterial();
                        productMaterial.setMaterialCode(materialCode);
                        ProductMaterial localMaterialConfigData = productMaterialMapper.selectOne(new QueryWrapper<>(productMaterial));
                        throw new NullPointerException("出站物料集合中缺失【"+localMaterialConfigData.getMaterialName()+"】批次物料！");
                    }
                    throw e;
                }
            }

            //过滤 单件物料BOM
            List<ProductBomDetail> dataMaterialSingleBom = productBomDetails.stream().filter(p -> p.getCodingType().equals(DictValueEnum.MATERIAL_TYPE_SINGLE.getValue())).collect(Collectors.toList());
            // 校验单件物料
            if(!CollectionUtils.isEmpty(dataMaterialSingleBom)){
                // 获取绑定的单件码物料信息
                List<DataMaterialSingle> dataMaterialSingleList = dataMaterialSingleMapper.selectList(new LambdaQueryWrapper<DataMaterialSingle>()
                        .eq(DataMaterialSingle::getProductSn, requestParam.getParam().getProductSn())// 产品SN
                        .eq(DataMaterialSingle::getOperationCode, requestParam.getCraftOperation().getOperationCode())// 工序编码
                        .eq(DataMaterialSingle::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                        .eq(DataMaterialSingle::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                        .eq(DataMaterialSingle::getStationCode, requestParam.getFactoryStation().getModelCode())// 工位编码
                        .eq(DataMaterialSingle::getDeviceCode, requestParam.getDeviceMain().getDeviceCode())// 设备编码
                );

                //将库中的批次物料绑定记录根据getMaterialCode分组计数
                Map<String, List<DataMaterialSingle>> singleMaterialCodeGroup = dataMaterialSingleList.stream().collect(Collectors.groupingBy(DataMaterialSingle::getMaterialCode));
                //循环判断MES中配置的物料用量和实际上传数量是否一致
                String materialCode=null;
                try{
                    for (ProductBomDetail productBomDetail : dataMaterialSingleBom) {
                        materialCode=productBomDetail.getMaterialCode();
                        //以materialCode为key获取上传物料集合，如果抛NPE就说明BOM中配置的materialCode出站物料集合中未上传
                        List<DataMaterialSingle> dataMaterialSingles = singleMaterialCodeGroup.get(productBomDetail.getMaterialCode());
                        //批次物料无论BOM配置用量是多少出站都只需要传一条物料参数
                        if(dataMaterialSingles.size()!=productBomDetail.getQuantity()){
                            //根据materialCode获取物料对象
                            ProductMaterial productMaterial=new ProductMaterial();
                            productMaterial.setMaterialCode(productBomDetail.getMaterialCode());
                            ProductMaterial localMaterialConfigData = productMaterialMapper.selectOne(new QueryWrapper<>(productMaterial));
                            throw new RuntimeException("上传物料数量与BOM不符！物料:【"+localMaterialConfigData.getMaterialName()
                                    +"】BOM配置用量为"+productBomDetail.getQuantity()
                                    +"，实际上传数量为"+singleMaterialCodeGroup.get(productBomDetail.getMaterialCode()).size());
                        }
                    }
                }catch (Exception e){
                    if(e instanceof NullPointerException){
                        //根据materialCode获取物料对象
                        ProductMaterial productMaterial=new ProductMaterial();
                        productMaterial.setMaterialCode(materialCode);
                        ProductMaterial localMaterialConfigData = productMaterialMapper.selectOne(new QueryWrapper<>(productMaterial));
                        throw new NullPointerException("出站物料集合中缺失【"+localMaterialConfigData.getMaterialName()+"】物料！");
                    }
                    throw e;
                }
            }
        }

        // 且过程参数数据不为空，查看所有过程参数并判断是否有NG，如果有则更新过站数据结果为NG。
//        if (DictValueEnum.RESULT_OK.getValue().equals(requestParam.getParam().getResult())) {

        // 过程数据和工艺参数，判断过站结果
        if (!CollectionUtils.isEmpty(dataCourseParams) && !CollectionUtils.isEmpty(craftParamDetailList)) {

            // List转Map
            Map<String, DataCourseParam> paramMap = dataCourseParams.stream().collect(Collectors.toMap(DataCourseParam::getParamCode, Function.identity(), (key1, key2) -> key2));

            // 产品结果OK，就需要校验参数判断最终总结果。
            if (DictValueEnum.RESULT_OK.getValue().equals(requestParam.getParam().getResult())) {
                //迭代前需要去除isNecessary=0的参数项
                List<CraftParamDetail> filterCraftParamDetailList= craftParamDetailList.stream().filter(craftParamDetail -> "1".equals(craftParamDetail.getIsNecessary())).collect(Collectors.toList());
                for (CraftParamDetail cpd : filterCraftParamDetailList) {

                    // 上传的参数OutboundServiceImpl
                    DataCourseParam param = paramMap.get(cpd.getParamCode());

                    // 不需要二次校验
                    if (DictValueEnum.NO.getValue().equals(cpd.getIsCheck())) {
                        if (DictValueEnum.RESULT_OK.getValue().equals(param.getParamResult())) {
                            continue;// 判断结果是否为OK
                        } else {
                            // 结果非OK，设置总结果NG，跳出循环。
                            requestParam.getParam().setResult(DictValueEnum.RESULT_NG.getValue());
                            break;
                        }
                    }

                    BigDecimal bd = new BigDecimal(param.getParamValue());
                    // 对比结果，传值.compareTo(设置值): 结果: -1：小于； 0 ：等于； 1 ：大于；
                    boolean re = bd.compareTo(cpd.getParamUpper()) == 1 || bd.compareTo(cpd.getParamLower()) == -1;
                    // 设置产品结果
                    requestParam.getParam().setResult(re ? DictValueEnum.RESULT_NG.getValue() : DictValueEnum.RESULT_OK.getValue());
                    // 如果有某个参数是NG，直接跳出循环，然后设置过站数据结果。
                    if (re) break;
                }
            }
        }

        DataPassStation ps = new DataPassStation();
        ps.setId(dataPassStation.getId());
        ps.setResult(requestParam.getParam().getResult());// 设置出站总结果
        ps.setExitTime(new Date());// 设置出站时间
        ps.setFlawCode(requestParam.getParam().getFlawCode());//缺陷代码
        // 判断是否返修，如果是，则返修次数+1
        if (DictValueEnum.REPAIR_SIGN_REPAIR.getValue().equals(dataPassStation.getProcessedMode())) {
            ps.setRepairNum(dataPassStation.getRepairNum() == null ? 1 : dataPassStation.getRepairNum() + 1);
        }
        // 更新过站数据
        dataPassStationMapper.updateById(ps);

        // 获取当前工艺路线
        DataProductPath dppCondition = new DataProductPath();
        dppCondition.setProductSn(dataMain.getProductSn());
        dppCondition.setOperationCode(requestParam.getCraftOperation().getOperationCode());
        DataProductPath currDataProductPath = dataProductPathMapper.selectOne(new QueryWrapper<>(dppCondition));
        if (currDataProductPath == null) throw new RuntimeException("产品编码【"+requestParam.getParam().getProductSn()+"】工艺错误！没有对应的工艺流程！");

        //更新工艺路线
        currDataProductPath.setRepairSign(dataPassStation.getProcessedMode());// 返修标识
        currDataProductPath.setResult(requestParam.getParam().getResult());// 工序结果
        dataProductPathMapper.updateById(currDataProductPath);

        //更新主表状态(如果状态为OK && 当前工序不是最后一个工序，把状态设置为未完成状态)
        if (DictValueEnum.RESULT_OK.getValue().equals(requestParam.getParam().getResult()) && !StringUtils.isEmpty(currDataProductPath.getNextStationCode())) {
            dataMain.setResult(DictValueEnum.RESULT_UN.getValue());// 未完成
        } else if (DictValueEnum.RESULT_OK.getValue().equals(requestParam.getParam().getResult()) && StringUtils.isEmpty(currDataProductPath.getNextStationCode())) {
            dataMain.setResult(DictValueEnum.RESULT_OK.getValue());// 合格
        } else {
            dataMain.setResult(DictValueEnum.RESULT_NG.getValue());// 不合格
        }

        // 没有下一个工位就设置主表【结束加工时间】和更新工单信息
        if (StringUtils.isEmpty(currDataProductPath.getNextStationCode())) {
            dataMain.setEndTime(new Date());
            // 更新主表信息
            dataMainMapper.updateById(dataMain);

            // 获取工单信息
            PlanWorkOrder planWorkOrderCondition = new PlanWorkOrder();
            planWorkOrderCondition.setWorkOrderCode(dataMain.getWorkOrderCode());
            PlanWorkOrder planWorkOrder = planWorkOrderMapper.selectOne(new QueryWrapper<>(planWorkOrderCondition));

            if (planWorkOrder != null) {
                // 获取主表信息
                List<DataMain> dataMainList = dataMainMapper.selectList(new LambdaQueryWrapper<DataMain>()
                        .eq(DataMain::getLienCode, requestParam.getFactoryLine().getModelCode())// 产线编码
                        .eq(DataMain::getSectionCode, requestParam.getFactorySection().getModelCode())// 工段编码
                        .eq(DataMain::getWorkOrderCode, dataMain.getWorkOrderCode())// 工单号
                        .select(DataMain::getResult)// 只查询【结果】字段
                );
                if (!CollectionUtils.isEmpty(dataMainList)) {
                    // 按结果分组
                    Map<String, Long> resultMap = dataMainList.stream().collect(Collectors.groupingBy(DataMain::getResult, Collectors.counting()));

                    // 统计数量
                    int okQty = resultMap.get(DictValueEnum.RESULT_OK.getValue()) == null ? 0 : resultMap.get(DictValueEnum.RESULT_OK.getValue()).intValue();// NG数量
                    int ngQty = resultMap.get(DictValueEnum.RESULT_NG.getValue()) == null ? 0 : resultMap.get(DictValueEnum.RESULT_NG.getValue()).intValue();// OK数量
                    int unQty = resultMap.get(DictValueEnum.RESULT_UN.getValue()) == null ? 0 : resultMap.get(DictValueEnum.RESULT_UN.getValue()).intValue();// 未完成数量
                    int scQty = resultMap.get(DictValueEnum.RESULT_SC.getValue()) == null ? 0 : resultMap.get(DictValueEnum.RESULT_SC.getValue()).intValue();// 报废数量
                    int totalQty = dataMainList.size();// 总数量

                    planWorkOrder.setTotalQty(totalQty);//条码数量
                    planWorkOrder.setFinishQty(okQty);//OK数量
                    planWorkOrder.setFailQty(ngQty); //NG数量
                    planWorkOrder.setOnlineQty(unQty);//在线数量
                    planWorkOrder.setUnfinishQty(planWorkOrder.getRequiredQty() - okQty < 0 ? 0 : planWorkOrder.getRequiredQty() - okQty);
                    planWorkOrder.setScrapQty(scQty);

                    //如果合格数量大于或者等于工单数量则更新工单为已完成
                    if (okQty  >= planWorkOrder.getRequiredQty()) {
                        //设置状态为已完成
                        planWorkOrder.setOrderStatus(DictValueEnum.WORK_STATUS_FINISH.getValue());
                    }
                    // 更新工单
                    planWorkOrderMapper.updateById(planWorkOrder);

                }
            }
        } else {
            // 更新主表信息
            dataMainMapper.updateById(dataMain);
        }

//        }
    }
}

